package mutexBank

import (
	"gitee.com/kingzyt/common/objPool"
	"runtime"
	"sort"
	"sync"
)

/*
对于大数量的需要保证线程安全的对象而言,若互相之间没有什么关联,都是独立的对象,
使用一般做法,每一个对象配备一个mutex是一种浪费,因为cpu能实际能并发的量不外乎core数量上下,
所以没有必要给每一个对象一个mutex,而是将所有对象分为N组,比如core的2倍数量的组,

这里适用的前提就是,各个对象的使用没有特殊的集中特性,都是随机平均分散,而且对象对于锁的使用和释放是迅速的,
这样就保证不会有一个mutex会被大量对象集中等待,所以这种方法就能够保证并发的效率,
而且即使对象是不断生成和销毁或是重用,都不会影响mutex,不会无意义地大量生成和销毁mutex,
用固定的mutex来保证大量对象的并发,效率和空间都能得到保证,

缺点是,必须在使用时保证lock和unlock的配对,不然一个对象的问题会死锁一批对象,
不过一般使用上只要lock和defer unlock是一起使用的,就没有问题

锁组数量太少的话,容易造成明明core有闲着的,但有一堆对象阻塞等待,
太多的话,core资源紧张的情况下,也都是挨个执行,再多也没有实际的帮助,
所以找一个不大不小差不多的值即可,比如在10~50之间貌似比较合适
*/
type MutexBank struct {
	ms       []*sync.Mutex
	groupCnt int64
}

func NewMutexBank(groupCnt int) *MutexBank {
	if groupCnt <= 0 {
		groupCnt = runtime.NumCPU() * 5
	}

	rlt := &MutexBank{ms: make([]*sync.Mutex, groupCnt), groupCnt: int64(groupCnt)}
	rlt.init()
	return rlt
}

func (self *MutexBank) init() {
	for i := 0; i < len(self.ms); i++ {
		self.ms[i] = &sync.Mutex{}
	}
}

func (self *MutexBank) Lock(srcId int64) {
	self.ms[srcId%self.groupCnt].Lock()
}
func (self *MutexBank) Unlock(srcId int64) {
	self.ms[srcId%self.groupCnt].Unlock()
}
func (self *MutexBank) Lock2Id(srcId1 int64, srcId2 int64) {
	idx1 := srcId1 % self.groupCnt
	idx2 := srcId2 % self.groupCnt
	if idx1 > idx2 {
		self.ms[idx1].Lock()
		self.ms[idx2].Lock()
	} else if idx1 < idx2 {
		self.ms[idx2].Lock()
		self.ms[idx1].Lock()
	} else {
		// same idx only lock once
		self.ms[idx1].Lock()
	}
}
func (self *MutexBank) Unlock2Id(srcId1 int64, srcId2 int64) {
	idx1 := srcId1 % self.groupCnt
	idx2 := srcId2 % self.groupCnt
	if idx1 > idx2 {
		self.ms[idx2].Unlock()
		self.ms[idx1].Unlock()
	} else if idx1 < idx2 {
		self.ms[idx1].Unlock()
		self.ms[idx2].Unlock()
	} else {
		// same idx only unlock once
		self.ms[idx1].Unlock()
	}
}

var g_mutexBankInt64ListPool = objPool.NewObjPool("g_mutexBankInt64ListPool", 0, 0, func() interface{} {
	return make([]int64, 50)
})

func (self *MutexBank) LockIds(ids []int64) (idxs []int64) {
	idxs = g_mutexBankInt64ListPool.Pull().([]int64)
	idxs = idxs[:0]
	for i := 0; i < len(ids); i++ {
		idx := ids[i] % self.groupCnt
		found := false
		for k := 0; k < len(idxs); k++ {
			if idxs[k] == idx {
				found = true
				break
			}
		}
		if !found {
			idxs = append(idxs, idx)
		}
	}
	sort.SliceStable(idxs, func(i, j int) bool {
		return idxs[i] >= idxs[j]
	})
	for i := 0; i < len(idxs); i++ {
		self.ms[idxs[i]].Lock()
	}
	return
}

// idxs is from LockIds's return
func (self *MutexBank) UnlockIds(idxs []int64) {
	for i := len(idxs) - 1; i >= 0; i-- {
		self.ms[idxs[i]].Unlock()
	}
	g_mutexBankInt64ListPool.Push(idxs)
}

func (self *MutexBank) LockAllGroup() {
	for i := len(self.ms) - 1; i >= 0; i-- {
		self.ms[i].Lock()
	}
}
func (self *MutexBank) UnlockAllGroup() {
	for i := 0; i < len(self.ms); i++ {
		self.ms[i].Unlock()
	}
}
